/*******************************************************************************
 *     Cloud Foundry
 *     Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
 *
 *     This product is licensed to you under the Apache License, Version 2.0 (the "License").
 *     You may not use this product except in compliance with the License.
 *
 *     This product includes a number of subcomponents with
 *     separate copyright notices and license terms. Your use of these
 *     subcomponents is subject to the terms and conditions of the
 *     subcomponent's license, as noted in the LICENSE file.
 *******************************************************************************/
package org.cloudfoundry.identity.uaa.login;

import com.fasterxml.jackson.core.type.TypeReference;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal;
import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import java.util.Arrays;
import java.util.Map;

import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf;
import static org.cloudfoundry.identity.uaa.test.SnippetUtils.fieldWithPath;
import static org.cloudfoundry.identity.uaa.test.SnippetUtils.headerWithName;
import static org.cloudfoundry.identity.uaa.test.SnippetUtils.parameterWithName;
import static org.springframework.http.HttpHeaders.ACCEPT;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.payload.JsonFieldType.ARRAY;
import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN;
import static org.springframework.restdocs.payload.JsonFieldType.OBJECT;
import static org.springframework.restdocs.payload.JsonFieldType.STRING;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class LoginInfoEndpointDocs extends InjectedMockContextTest {


    @Test
    public void info_endpoint_for_json() throws Exception {
        Snippet requestParameters = requestParameters(
                parameterWithName("origin").optional(null).type(STRING).description("Use the configured prompts of the OpenID Connect Provider with the given origin key in the response. Fallback to zone values if no prompts are configured or origin is invalid.")
        );

        Snippet responseFields = responseFields(
            fieldWithPath("app.version").type(STRING).description("The UAA version"),
            fieldWithPath("commit_id").type(STRING).description("The GIT sha for the UAA version"),
            fieldWithPath("timestamp").type(STRING).description("JSON timestamp for the commit of the UAA version"),
            fieldWithPath("idpDefinitions").optional().type(OBJECT).description("A list of alias/url pairs of SAML IDP providers configured. Each url is the starting point to initiate the authentication process for the SAML identity provider."),
            fieldWithPath("idpDefinitions.*").optional().type(ARRAY).description("A list of alias/url pairs of SAML IDP providers configured. Each url is the starting point to initiate the authentication process for the SAML identity provider."),
            fieldWithPath("links").type(OBJECT).description("A list of alias/url pairs of configured action URLs for the UAA"),
            fieldWithPath("links.login").type(STRING).description("The link to the login host alias of the UAA"),
            fieldWithPath("links.uaa").type(STRING).description("The link to the uaa alias host of the UAA"),
            fieldWithPath("links.passwd").type(STRING).description("The link to the 'Forgot Password' functionality. Can be external or internal to the UAA"),
            fieldWithPath("links.register").type(STRING).description("The link to the 'Create Account' functionality. Can be external or internal to the UAA"),
            fieldWithPath("entityID").type(STRING).description("The UAA is always a SAML service provider. This field contains the configured entityID"),
            fieldWithPath("prompts").type(OBJECT).description("A list of name/value pairs of configured prompts that the UAA will login a user. Format for each prompt is [type, display name] where type can be 'text' or 'password'"),
            fieldWithPath("prompts.username").type(ARRAY).description("Information about the username prompt."),
            fieldWithPath("prompts.password").type(ARRAY).description("Information about the password prompt."),
            fieldWithPath("prompts.passcode").optional().type(ARRAY).description("If a SAML identity provider is configured, this prompt contains a URL to where the user can initiate the SAML authentication flow."),
            fieldWithPath("zone_name").type(STRING).description("The name of the zone invoked"),
            fieldWithPath("showLoginLinks").optional(false).type(BOOLEAN).description("Set to true if there are SAML or OAUTH/OIDC providers with a visible link on the login page.")
        );

        Snippet requestHeaders = requestHeaders(
            headerWithName(ACCEPT).description("When set to accept " + APPLICATION_JSON_VALUE + " the server will return prompts and server info in JSON format.")
        );

        getMockMvc().perform(get("/info")
            .param("origin","oidc-provider")
            .header(ACCEPT, APPLICATION_JSON_VALUE))
            .andExpect(status().isOk())
            .andDo(
                document("{ClassName}/{methodName}",
                         preprocessResponse(prettyPrint()),
                         requestHeaders,
                         requestParameters,
                         responseFields)
            );
    }

    @Test
    public void user_ui_login() throws Exception {
        Snippet requestParameters = requestParameters(
            parameterWithName("username").required().type(STRING).description("The username of the user, sometimes the email address."),
            parameterWithName("password").required().type(STRING).description("The user's password"),
            parameterWithName("X-Uaa-Csrf").required().type(STRING).description("Automatically configured by the server upon /login. Must match the value of the X-Uaa-Csrf cookie.")
        );
        Snippet requestHeaders = requestHeaders(
            headerWithName("Cookie").required().type(STRING).description("Must contain the a value for the cookie X-Uaa-Csrf and that must match the request parameter of the same name")
        );

        getMockMvc().perform(
            post("/login.do")
                .with(cookieCsrf())
                .header("Cookie","X-Uaa-Csrf=12345a")
                .param("username", "marissa")
                .param("password", "koala")
                .param("X-Uaa-Csrf", "12345a"))
            .andDo(
                document("{ClassName}/{methodName}",
                preprocessResponse(prettyPrint()),
                requestHeaders,
                requestParameters))
            .andExpect(status().isFound())
            .andExpect(redirectedUrl("/"));
    }

    @Test
    public void invalid_request() throws Exception {
        getMockMvc().perform(get("/invalid_request"))
            .andDo(
                document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()))
            )
            .andExpect(status().isOk());
    }

    @Test
    public void passcode_request() throws Exception {
        ScimUserProvisioning userProvisioning = getWebApplicationContext().getBean(JdbcScimUserProvisioning.class);
        ScimUser marissa = userProvisioning.query("username eq \"marissa\" and origin eq \"uaa\"", IdentityZoneHolder.get().getId()).get(0);
        UaaPrincipal uaaPrincipal = new UaaPrincipal(marissa.getId(), marissa.getUserName(), marissa.getPrimaryEmail(), marissa.getOrigin(), marissa.getExternalId(), IdentityZoneHolder.get().getId());
        UaaAuthentication principal = new UaaAuthentication(uaaPrincipal, Arrays.asList(UaaAuthority.fromAuthorities("uaa.user")), null);

        MockHttpSession session = new MockHttpSession();
        session.setAttribute(
            HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
            new MockMvcUtils.MockSecurityContext(principal)
        );

        MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/passcode")
            .accept(APPLICATION_JSON_VALUE)
            .session(session)
            .header("Cookie","JSESSIONID="+session.getId());

        getMockMvc().perform(get)
            .andDo(
                document("{ClassName}/{methodName}",
                         preprocessResponse(prettyPrint()),
                         requestHeaders(headerWithName("Cookie").required().description("JSESSIONID cookie to match the server side session of the authenticated user."))
                )
            )

            .andExpect(status().isOk());
    }

    @Test
    public void generate_auto_login_code() throws Exception {
        generate_auto_login_code(true);
    }
    public Map<String,Object> generate_auto_login_code(boolean x) throws Exception {
        Snippet requestFields = requestFields(
            fieldWithPath("username").required().type(STRING).description("The username for the autologin request"),
            fieldWithPath("password").required().type(STRING).description("The password for the autologin request")
        );
        Snippet requestHeaders = requestHeaders(
            headerWithName("Authorization").required().description("Basic authorization header for the client making the autologin request"),
            headerWithName("Content-Type").required().description("Set to "+APPLICATION_JSON_VALUE),
            headerWithName("Accept").required().description("Set to "+APPLICATION_JSON_VALUE)
        );
        Snippet responseFields = responseFields(
            fieldWithPath("code").required().type(STRING).description("The code used to authenticate the user."),
            fieldWithPath("path").optional(null).type(STRING).description("Not used. Hardcoded to /oauth/authorize")
        );
        AutologinRequest request = new AutologinRequest();
        request.setUsername("marissa");
        request.setPassword("koala");
        String body = getMockMvc().perform(
            post("/autologin")
                .header("Authorization", "Basic " + new String(new Base64().encode("admin:adminsecret".getBytes())))
                .contentType(APPLICATION_JSON)
                .accept(APPLICATION_JSON)
                .content(JsonUtils.writeValueAsString(request)))
            .andDo(
                document("{ClassName}/{methodName}",
                         preprocessResponse(prettyPrint()),
                         requestHeaders,
                         requestFields,
                         responseFields
                )
            )
            .andExpect(status().isOk())
            .andReturn().getResponse().getContentAsString();
        return JsonUtils.readValue(body, new TypeReference<Map<String, Object>>() {});
    }

    @Test
    public void perform_auto_login() throws Exception {
        Map<String,Object> code = generate_auto_login_code(true);
        Snippet requestParameters = requestParameters(
            parameterWithName("code").required().type(STRING).description("The code generated from the POST /autologin"),
            parameterWithName("client_id").required().type(STRING).description("The client_id that generated the autologin code")
        );
        getMockMvc().perform(MockMvcRequestBuilders.get("/autologin")
                                 .param("code", (String)code.get("code"))
                                 .param("client_id", "admin"))
            .andDo(print())
            .andDo(
                document("{ClassName}/{methodName}",
                         preprocessResponse(prettyPrint()),
                         requestParameters
                )
            )
            .andExpect(redirectedUrl("home"));
    }

}
